﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mesuro.Equation {
	public class EqSymbol {

		private EqSymbolDefinition defHead, defTail, defActive;
		private int definitionCount = 0;

		private readonly String _key;
		public String Key {
			get { return _key; }
		}

		public EqSymbolFlags Flags {
			get { return defActive.Flags; }
		}

		public EqTerm Value {
			get { return defActive.Value; }
			set { defActive.Value = value; }
		}

		public int ExpectedArguments { get { return defActive.ExpectedArguments; } }

		//cctor
		internal EqSymbol( String sym ) {
			_key = sym;
		}

		internal bool SelectDefinition( int nArgs ) {
			defActive = defHead;

			while (defActive != null && defActive.ExpectedArguments < nArgs)
				defActive = defActive.Next;

			return defActive != null && defActive.ExpectedArguments == nArgs;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="flags"></param>
		/// <exception cref="InvalidOperationException">If a definition with the same number of parameter already exists.</exception>
		internal void AddDefinition( EqSymbolDefinition definition ) {

			if (SelectDefinition( definition.Arguments.Count ))
				throw new InvalidOperationException( "A conflicting definition already exists." );

			if (defActive == null) {//If this wil be the last definition in the list so far
				definition.Previous = defTail;

				if (defTail == null)//This is the first definition
					defTail = defHead = definition;
				else
					defTail.Next = definition;

				defActive = defTail = definition;
				definitionCount++;

			} else {
				if (definition.Previous != null) defActive.Previous.Next = definition;
				defActive.Previous = definition;
				defActive = definition;
			}
		}

		public EqTerm Evaluate( EqScope hostScope, EqListTerm argList ) {
			if (argList == null || argList.Count == 0) {//0 Arguments
				//Ensure we're looking at the correct function
				SelectDefinition( 0 );

				//This is a variable so we should evaluate in the host newScope

				//Evaluate and return
				return defActive.Evaluate( hostScope, argList );

			} else {//1+ Arguments

				//Ensure we're looking at the correct function
				if (!SelectDefinition( argList.Count ))
					//Return null if there is no matching function
					return null;

				//Create a new newScope to evaluate the function in
				EqScope scope = hostScope.DeriveScope();

				//Set the values for the arguments
				for (int i = 0; i < argList.Count; i++)
					scope.CreateSymbol( defActive.Arguments[i].Key, Flags.HasFlag( EqSymbolFlags.HoldAll ) ? argList[i] : argList[i].Evaluate( hostScope ), EqSymbolFlags.WriteProtect );

				//Evaluate and return
				return defActive.Evaluate( scope, argList );

			}
		}

	}
	class EqSymbolDefinition {

		private readonly EqSymbolFlags _flags;
		public EqSymbolFlags Flags {
			get { return _flags; }
		}

		private readonly List<EqSymbolInstance> _arguments = new List<EqSymbolInstance>();
		public List<EqSymbolInstance> Arguments {
			get { return _arguments; }
		}

		private readonly int _nArgs;
		public int ExpectedArguments {
			get { return _nArgs; }
		}

		private EqSymbolDefinition _previous;
		public EqSymbolDefinition Previous {
			get { return _previous; }
			set { _previous = value; }
		}

		private EqSymbolDefinition _next;
		public EqSymbolDefinition Next {
			get { return _next; }
			set { _next = value; }
		}

		private EqTerm value;
		public virtual EqTerm Value {
			get { return this.value; }
			set { this.value = value; }
		}

		public EqSymbolDefinition( EqListTerm args, EqSymbolFlags flags ) {
			_flags = flags;

			if (args == null) {
				_nArgs = 0;
				return;
			}

			foreach (EqTerm t in (EqListTerm)args)
				if (t is EqSymbolInstance)
					Arguments.Add( (EqSymbolInstance)t );
				else
					throw new ArgumentException( "Non-symbol term found in formal argument definition: " + t, "args" );

			_nArgs = flags.HasFlag( EqSymbolFlags.VarArgs ) ? -1 : Arguments.Count;
		}

		public virtual EqTerm Evaluate( EqScope scope, EqListTerm arguments ) {
			return Value.Evaluate( scope );
		}

	}
	class EqExternalSymbolDefinition : EqSymbolDefinition {
		public override EqTerm Value {
			get { return new EqSymbolInstance( "#NativeFunction!" ); }
			set { throw new InvalidOperationException( "Cannot change value of native function" ); }
		}

		private readonly EqSymbolExternalEvaluator eval;

		public EqExternalSymbolDefinition( EqListTerm args, EqSymbolFlags flags, EqSymbolExternalEvaluator evaluator ) :
			base( args, flags | EqSymbolFlags.Permanent | EqSymbolFlags.WriteProtect ) {
			eval = evaluator;
		}

		public override EqTerm Evaluate( EqScope scope, EqListTerm arguments ) {
			if (arguments == null) throw new ArgumentNullException( "arguments" );
			return eval( arguments.EvaluateEach( scope ).ToArray() );
		}
	}
	public delegate EqTerm EqSymbolExternalEvaluator( EqTerm[] arguments );
	[Flags]
	public enum EqSymbolFlags { None = 0, WriteProtect = 1, Permanent = 2, VarArgs = 4, HoldAll = 8 }
}
